const zookeeper = require('node-zookeeper-client');
const AServiceRegistry = require('./AServiceRegistry');
const LoadBalancer = require('./loadbalancer/LoadBalancer');

/**
 * 使用Zookeeper作为微服务注册，发现和配置管理中心，节点树结构如下：

 *                        /service1/instance1
 *               /service/         \instance2
 *              /        \
 *             /          \service2/instance1
 * /microserv /                    \instance2
 *            \
 *             \
 *              \         /service1/key1
 *               \       /         \key2
 *                config/global/key1
 *                      \      \key2
 *                       \
 *                        \service2/key1
 *                                 \key2
 *
 * 根节点下创建service节点存储服务实例，config节点存储KV模式的配置参数
 * 
 * 监听服务节点，子节点有变化时候通知订阅者
 */
class ZkServiceRegistry extends AServiceRegistry {

    zkClient;
    loadBalancer;

    constructor(param) {
        super(param);

        this.loadBalancer = new LoadBalancer(param.SELECT_STRATEGY);

        var connectionString = `${param.nodes}/${param.NAME_SPACE}${param.BASE_PATH}${param.SERVICE_PATH}`;

        console.debug("创建 zk 客户端", connectionString);
        this.zkClient = zookeeper.createClient(connectionString);

        this.zkClient.once('connected', () => console.debug("zk 客户端连接成功"));
        this.zkClient.on('state', (state) => {
            console.info("on state", state)
        });

        this.zkClient.connect();
    }

    /**
     * 监控注册的服务变更事件,如服务实例加入，移除，配置数据变更
     * @param {Array<String>} services 监控的服务集合
     */
    subscribeEvent(services) {
        services.forEach(service => {
            loadChildren(this.zkClient, "/" + service, this.loadBalancer).catch(err => console.error(err));
        });
    }

    /**
     * 根据服务名称获取可访问的一个节点，如果有多个可用节点
     * @param {String} service 服务名称
     * @returns String 服务调用地址
     */
    getServiceNode(service) {
        return this.loadBalancer.getRealPath(service);
    }

    /**
     * 释放客户端
     */
    close() {
        console.debug("进入 close ")
        if (this.zkClient) {
            console.debug("执行 close")
            this.zkClient.close();
        }
    }
}

function loadChildren(zkClient, path, loadBalancer) {
    console.debug("进入 loadChildren ", path);
    return new Promise((resolve, reject) => {
        try {
            zkClient.getChildren(path, (event) => {
                console.debug(" loadChildren watcher ", path, event);

                loadChildren(zkClient, event.path, loadBalancer);
            }, (error, ids) => {
                console.debug(" loadChildren callback ", path, error, ids);
                if (error) {
                    reject({
                        ...error,
                        message: `获取ZK节点error，Path: ${path}`
                    });
                } else {
                    resolve(getData(zkClient, path, ids, loadBalancer));
                }
            });
        } catch (err) {
            console.error(err)
        }
    });
}

function getData(zkClient, path, ids, loadBalancer) {
    console.debug("进入 getData ");

    let pros = ids.map(id => new Promise((resolve, reject) => {
        //获取服务地址
        zkClient.getData(path + "/" + id, (error, data) => {
            console.debug(" getData callback ", path, id);
            if (error) {
                reject({
                    ...error,
                    message: `获取ZK服务地址error，Stack: ${err.stack}`
                });
            }
            if (!data) {
                reject({
                    ...error,
                    message: `ZK data is not exist`
                });
            }

            const node = JSON.parse(data).payload;
            const protocol = node.ssl ? "https://" : "http://";

            resolve(`${protocol}${node.host}:${node.port}`);
        });
    }));

    return Promise.all(pros).then(arr => loadBalancer.updateCache(path, arr));
}

module.exports = ZkServiceRegistry;